閉包算是在 JS 中常聽到,卻不容易使用的一個方法,更多狀況是不小心用出來,~~因此出 bug ~~
在介紹閉包之前,先來看看下面範例:
function randomString(length) {
      var result = '';
      var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      var charactersLength = characters.length;
      for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
      }
      return result;
    }
    function getData() {
      var demoData = [];
      for (let i = 0; i < 1000; i++) {
        demoData.push(randomString(1000))
      }
    }
    getData();
randomString 是一個會根據設定執行數次,產生亂碼字串的方法,而這亂碼字串會藉由 return result 回傳,最後又在 push 至 demoData 變數上。
使用 chrome 無痕模式來觀察 Memory ,可以發現在執行上述程式碼時,記憶體使用了 1.3 MB
再來執行另外一段非常相近的程式碼,來看看他的記憶體使用量:
function randomString(length) {
      var result = '';
      var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      var charactersLength = characters.length;
      for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
      }
      return result;
    }
    
    var demoData = [];
    function getData() {
      for (let i = 0; i < 1000; i++) {
        demoData.push(randomString(1000))
      }
    }
    getData();

可以發現這個範例記憶體增使用量將近 21MB ,這是因為第二個範例 demoData 變數是被放在外層也就是全域( window)底下,而 demoData 變數此時是能在被呼叫、使用的。
而第一個範例中, demoData 是在函示裡頭, demoData 變數則會跟著 getData 執行完畢時,一同被釋放記憶體,此時 demoData  也是無法被呼叫的,因此兩者記憶體落差十分大。
之所以要先講這一段是因為,記憶體可以說是閉包的一個重點。
接下先來看看閉包的一個簡單範例
function openFn() {
      let num = 10
      function ClosureFn(newNum) {
        num = num + newNum
        return num
      }
      //函示內 return 函示就會變成 『閉包』
      return ClosureFn
    }
const useClosure = openFn()
console.log(useClosure(10)) //20
console.log(useClosure(10)) //30
console.log(useClosure(10)) //40
在上面範例中 ClosureFn 其實就是閉包,若要執行這個閉包可以直接使用 openFn()(100)  其就會被執行,不過一般來說我們不會直接使用兩個 ()() 小刮號做執行,而是像上面範例中使用 openFn() 並且再用一個變數來做指向。
而上面有提到記憶體是閉包的重點,關於這一點我們可以看看連續執行 useClosure()  後回傳的值會不斷疊加,然而 ClosureFn 閉包函示內部雖然沒有  let num = 100 ,不過閉包內部會因為 num = num + newNum  這段程式碼,有使用到 num 變數,因此按照作用域的規則,會訪問(參考)外層函式的 let num  變數,因為這個訪問(參考)的動作,就會讓 num 變數的記憶體『不被釋放』,因此當正是因為這個『不被釋放』,我們使用 useClosure(10) 的值才可以不斷被疊加。
這邊也試者使用圖片來增加對閉包的理解:
之所以要使用閉包,就是因為可以透過不同變數、常數,讓閉包回傳資料各自獨立,某些需要重複使用程式碼的狀況就可以使用閉包,例如
function openFn() {
      let num = 10
      function ClosureFn(newNum) {
        num = num + newNum
        return num
      }
      //函示內 return 函示就會變成 『閉包』
      return ClosureFn
    }
const useClosure1 = openFn()
console.log(useClosure1(10))
console.log(useClosure1(10))
const useClosure2 = openFn()
console.log(useClosure2(100))
console.log(useClosure2(100))
下篇則會介紹閉包延伸運用。